package com.szsh.aiot.hsm.dao;

import com.google.common.base.Joiner;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.szsh.aiot.common.utils.EncryptUtil;
import com.szsh.aiot.common.utils.StringUtils;
import com.szsh.aiot.common.utils.UrlUtils;
import com.szsh.aiot.hsm.properties.WapLoginProperties;
import com.szsh.aiot.common.utils.XXTea;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

@Repository
public class WapLoginDao {
    private static final Logger LOGGER = LoggerFactory.getLogger(WapLoginDao.class);

    private final WapLoginProperties wapLoginProperties;

    private final RestTemplate restTemplate;

    private final Gson gson;

    public WapLoginDao(WapLoginProperties wapLoginProperties, RestTemplate restTemplate, Gson gson) {
        this.wapLoginProperties = wapLoginProperties;
        this.restTemplate = restTemplate;
        this.gson = gson;
    }

    /**
     * 天翼账号登录地址获取
     * @param returnUrl   登录成功返回的地址
     * @param appId       调用方appId
     * @param version     调用方密钥版本
     * @return 登录地址
     */
    public String getLoginUrl(String returnUrl, String appId, String version) {
        Map<String, Object> requestMap = new LinkedHashMap<>();

        requestMap.put("appId", wapLoginProperties.getAppId());
        requestMap.put("clientType", wapLoginProperties.getClientType());
        requestMap.put("format", wapLoginProperties.getFormat());
        requestMap.put("version", wapLoginProperties.getVersion());
        // 密钥
        String appSecret = wapLoginProperties.getAppSecret();

        // 对 parms 的加密
        Map<String, Object> parasMap = new HashMap<>();

        parasMap.put("timeStamp", System.currentTimeMillis());

        // 额外参数
        Map<String, Object> additionalParams = new HashMap<>();
        additionalParams.put("returnUrl", returnUrl);
        additionalParams.put("customAppId", appId);
        additionalParams.put("version", version);

        parasMap.put("returnURL", UrlUtils.urlEncoder(UrlUtils.httpUrlAddParam(wapLoginProperties.getLoginReturnUrl(), additionalParams)));

        // 模板ID
        if (StringUtils.isBlank(wapLoginProperties.getTemplateId())) {
            parasMap.put("templateId", 0);
        } else {
            parasMap.put("templateId", wapLoginProperties.getTemplateId());
        }

        // 其他登录类型
        if (StringUtils.isNotBlank(wapLoginProperties.getLoginType())) {
            parasMap.put("loginType", wapLoginProperties.getLoginType());
        }

        // 遇到问题链接
        if (StringUtils.isNotBlank(wapLoginProperties.getQaUrl())) {
            parasMap.put("qaUrl", UrlUtils.urlEncoder(wapLoginProperties.getQaUrl()));
        }

        // 其他登录方式链接(免密失败后跳转的自定义链接)
        if (StringUtils.isNotBlank(wapLoginProperties.getOtherLoginUrl())) {
            parasMap.put("otherLoginUrl", UrlUtils.urlEncoder(wapLoginProperties.getOtherLoginUrl()));
        }

        // 用于保持请求和回调的状态，登录请求后原样带回给第三方。
        // 该参数可用于防止csrf攻击（跨站请求伪造攻击），建议第三方带上该参数，可设置为简单的随机数加session进行校验
        parasMap.put("state", "");

        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("&").useForNull("").withKeyValueSeparator("=").join(parasMap);
            // 加密
            String paras = XXTea.encrypt(plainStr, appSecret);
            // 设置
            requestMap.put("paras", paras);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }
        // 签名
        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("").useForNull("").join(requestMap.values());
            // 加密
            String sign = EncryptUtil.hmacSHA1(plainStr, appSecret);
            // 设置
            requestMap.put("sign", sign);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("生成天翼账号登录地址,url:[" + wapLoginProperties.getLoginUrl() + "],requestMap:[" + requestMap + "]");
        }

        return UrlUtils.httpUrlAddParam(wapLoginProperties.getLoginUrl(), requestMap);
    }

    /**
     * 天翼账号退出地址获取
     * @param returnUrl 退出成功返回的地址
     * @return 退出地址
     */
    public String getLogOutUrl(String returnUrl) {
        Map<String, Object> requestMap = new LinkedHashMap<>();

        requestMap.put("appId", wapLoginProperties.getAppId());
        requestMap.put("clientType", wapLoginProperties.getClientType());
        requestMap.put("format", wapLoginProperties.getFormat());
        requestMap.put("version", wapLoginProperties.getVersion());
        // 密钥
        String appSecret = wapLoginProperties.getAppSecret();

        // 对 parms 的加密
        Map<String, Object> parasMap = new HashMap<>();

        parasMap.put("returnURL", UrlUtils.urlEncoder(
                UrlUtils.httpUrlAddParam(wapLoginProperties.getLogOutReturnUrl(), "returnUrl", returnUrl)));

        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("&").useForNull("").withKeyValueSeparator("=").join(parasMap);
            // 加密
            String paras = XXTea.encrypt(plainStr, appSecret);
            // 设置
            requestMap.put("paras", paras);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }
        // 签名
        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("").useForNull("").join(requestMap.values());
            // 加密
            String sign = EncryptUtil.hmacSHA1(plainStr, appSecret);
            // 设置
            requestMap.put("sign", sign);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("生成天翼账号退出地址,url:[" + wapLoginProperties.getLogOutUrl() + "],requestMap:[" + requestMap + "]");
        }

        return UrlUtils.httpUrlAddParam(wapLoginProperties.getLogOutUrl(), requestMap);
    }

    /**
     * 根据code获取手机号码
     * @param code 天翼账号返回的code
     * @return 手机号码
     */
    public String getPhoneNumber(String code) {
        String accessToken = getAccessToken(code);
        if (StringUtils.isBlank(accessToken)) {
            return null;
        }
        // 获取手机号码
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("天翼账号平台accessToken[{}]", accessToken);
        }

        // 根据accessToken获取手机号码
        Map<String, Object> requestMap = new LinkedHashMap<>();

        requestMap.put("clientId", wapLoginProperties.getAppId());
        requestMap.put("format", wapLoginProperties.getParamFormat());

        // 参数
        Map<String, String> businessParamsMap = new HashMap<>();
        businessParamsMap.put("accessToken", accessToken);
        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("&").useForNull("").withKeyValueSeparator("=").join(businessParamsMap);
            // 加密
            String paras = XXTea.encrypt(plainStr, wapLoginProperties.getAppSecret()).toLowerCase();
            // 设置
            requestMap.put("params", paras);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }
        // 时间戳
        requestMap.put("timeStamp", System.currentTimeMillis());

        // 签名
        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("").useForNull("").join(requestMap.values());
            // 加密
            String sign = EncryptUtil.sha1WithRSAByPrivateKey(plainStr, wapLoginProperties.getPrivateKey());
            // 设置
            requestMap.put("sign", Hex.encodeHexString(Base64.decodeBase64(sign)));
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }

        // 执行请求
        JsonObject returnJsonObject = post(wapLoginProperties.getUserInfoUrl(), requestMap);
        if (null == returnJsonObject) {
            return null;
        }
        if (returnJsonObject.has("data")) {
            JsonElement jsonElement = returnJsonObject.get("data");
            if (jsonElement.isJsonPrimitive()) {
                String data = jsonElement.getAsString();
                // rsa解密
                try {
                    String userInfo = EncryptUtil.decryptByPrivateKey(Base64.encodeBase64String(Hex.decodeHex(data)),
                            wapLoginProperties.getPrivateKey(), EncryptUtil.RSA_KEY_SIZE_1024);
                    if (LOGGER.isInfoEnabled()) {
                        LOGGER.info("用户信息.[{}]", userInfo);
                    }
                    JsonObject userInfoJson = gson.fromJson(userInfo, JsonObject.class);
                    // 返回手机号码
                    return userInfoJson.get("mobile").getAsString();
                } catch (Exception e) {
                    if (LOGGER.isErrorEnabled()) {
                        LOGGER.error("系统异常", e);
                    }
                }
            }
        }
        return null;
    }

    /**
     * 根据code获取accessToken
     * @param code code
     * @return accessToken
     */
    private String getAccessToken(String code) {
        Map<String, Object> requestMap = new LinkedHashMap<>();

        requestMap.put("appId", wapLoginProperties.getAppId());
        requestMap.put("code", code);
        requestMap.put("format", wapLoginProperties.getParamFormat());
        requestMap.put("grantType", wapLoginProperties.getGrantType());

        // 签名
        try {
            // 拼接成 字符串
            String plainStr = Joiner.on("").useForNull("").join(requestMap.values());
            // 加密
            String sign = EncryptUtil.hmacSHA1(plainStr, wapLoginProperties.getAppSecret());
            // 设置
            requestMap.put("sign", sign);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("加密失败", e);
            }
            return null;
        }

        // 执行请求
        JsonObject returnJsonObject = post(wapLoginProperties.getAccessTokenUrl(), requestMap);
        if (null == returnJsonObject) {
            return null;
        }
        if (returnJsonObject.has("accessToken")) {
            JsonElement jsonElement = returnJsonObject.get("accessToken");
            if (jsonElement.isJsonPrimitive()) {
                return jsonElement.getAsString();
            }
        }
        return null;
    }

    /**
     * 执行请求
     * @param url 请求地址
     * @param parasMap 参数
     * @return null:调用失败
     */
    private JsonObject post(String url, Map<String, Object> parasMap) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("请求处理,url:[{}],parasMap:[{}]", url, parasMap);
        }

        MultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap<>();
        parasMap.forEach((k, v) -> multiValueMap.add(k, Objects.toString(v, "")));

        // 发送请求
        String resultStr;
        try {
            resultStr = restTemplate.postForObject(url, multiValueMap, String.class);
        } catch (Exception e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("调用天翼账号接口异常", e);
            }
            return null;
        }
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("返回结果:[{}]", resultStr);
        }

        if (StringUtils.isNotBlank(resultStr)) {
            try {
                return gson.fromJson(resultStr, JsonObject.class);
            } catch (Exception e) {
                if (LOGGER.isErrorEnabled()) {
                    LOGGER.error("系统异常", e);
                }
                return null;
            }
        }
        return null;
    }
}
